/**
 * 	 DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *   @author Colin Redmond
 *   Copyright (C) 2011 Colin Redmond
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package com.cred.industries.core.server;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cred.industries.core.database.DBConnectionManager;
import com.cred.industries.core.exceptions.ApplicationException;
import com.cred.industries.core.transferObj.result.ReturnCodes;
import com.cred.industries.core.util.ReadWriteAutoLock;

/**
 * configuration variables used in the program. They are stored in a key value pair
 * and can be either int, string or float.
 */
public final class ServerConfig {
	
	private static final Logger logger = LoggerFactory.getLogger(DBConnectionManager.class);

	private static final String SERVER_CONFIG_NAME = "NAME";
	private static final String SERVER_CONFIG_VALUE = "VALUE";
	private static final String DATA_TYPE_VALUE = "TYPENAME";
	private static final ServerConfig sInstance = new ServerConfig();
	
	protected Map<String, Object> mConfigVariables = new HashMap<>();
	
	private ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
	
	private ServerConfig() {
				
		init();
	}
	
	/**
	 * builds the key values pairs based on data in the Db.
	 * This class is thread safe. It uses a read/write lock 
	 * so we can have multiple reads at the same time
	 */
	private void init() {
		
		logger.info("initializing the server config.");
		try (Connection connect = DBConnectionManager.getInstance().getConnection();
				ReadWriteAutoLock lock = ReadWriteAutoLock.writeLock(mReadWriteLock)) {	
			
			mConfigVariables.clear();
			
			logger.info("loading int values");
			//first lets get all the int config variables. 
			String sqlQuery = "SELECT serverconfig.name, serverconfig.value, dataTypes.typeName "
					+ "FROM serverconfig INNER JOIN dataTypes ON serverconfig.typeId = dataTypes.typeId ";
			int numValues = 0;
			int stringValues = 0;
			int floatValues = 0;
			int intValues = 0;
			
			try (Statement statement = connect.createStatement();
					ResultSet resultSet = statement.executeQuery(sqlQuery);) {
				
				while(resultSet.next()) {
					numValues++;
					String type = resultSet.getString(DATA_TYPE_VALUE);
					
					switch (type) {
					case "STRING": 
						stringValues++;
						mConfigVariables.put(resultSet.getString(SERVER_CONFIG_NAME), resultSet.getString(SERVER_CONFIG_VALUE));
						break;
					case "INT": 
						intValues++;
						mConfigVariables.put(resultSet.getString(SERVER_CONFIG_NAME), Integer.parseInt(resultSet.getString(SERVER_CONFIG_VALUE)));
						break;
					case "FLOAT":
						floatValues++;
						mConfigVariables.put(resultSet.getString(SERVER_CONFIG_NAME), Float.parseFloat(resultSet.getString(SERVER_CONFIG_VALUE)));
						break;
					}
				}
			}
			logger.info("Number of int server config values found " + numValues + " string: " + stringValues + ", float: " + floatValues + ", int: " + intValues);
			
			
					
		} catch (SQLException e) {
			throw new ApplicationException(e, ReturnCodes.DATABASE_ERROR, "Failed populating ServerConfig");
		} catch (NumberFormatException e) {
			throw new ApplicationException(e, ReturnCodes.UNKNOWN_ERROR, "unable to parse numbers from db");
		}		
	}
	
	public static ServerConfig getInstance() {

		return sInstance;
	}
	
	/**
	 * clears the key values pairs then rebuilds them from the Db
	 */
	public void refresh() {
		
		init();
	}
	
	/**
	 * 
	 * @param type class type we are looking for
	 * @param key the key we are looking for, the supported types are Integer, Float and String
	 * @return true if the key is in the DB
	 */
	public <T> boolean hasValue(Class<T> type, ServerConfigEnum key) {
		
		return hasValue(type, key.toString());
	}
	
	/**
	 * 
	 * @param type class type we are looking for
	 * @param key the key we are looking for, the supported types are Integer, Float and String
	 * @return true if the key is in the DB
	 */
	public <T> boolean hasValue(Class<T> type, String key) {
		
		try (ReadWriteAutoLock lock = ReadWriteAutoLock.readLock(mReadWriteLock)) {
			return mConfigVariables.containsKey(key.toLowerCase());
		}
	}
	
	/**
	 * 
	 * @param type class type we are looking for
	 * @param key the key we are looking for 
	 * @return the value that matches the key
	 */
	public <T> T getValue(Class<T> type, ServerConfigEnum key) {
		
		return getValue(type, key.toString());
	}
	
	/**
	 * 
	 * @param type class type we are looking for
	 * @param key the key we are looking for 
	 * @return
	 */
	public <T> T getValue(Class<T> type, String key) {
		
		try (ReadWriteAutoLock lock = ReadWriteAutoLock.readLock(mReadWriteLock)) {
			
			String keyString = key.toLowerCase();
			if(mConfigVariables.containsKey(keyString)) {
				return type.cast(mConfigVariables.get(keyString));
			}
				
			return null;
		}
	}
		
	/**
	 * takes in the config variable and returns the type if any
	 * @param key name of the config variable we are looking for
	 * @return returns the type if any
	 */
	@SuppressWarnings("rawtypes")
	public Class getType(ServerConfigEnum key) {
		
		return getType(key.toString());
	}
		
	/**
	 * takes in the config variable and returns the type if any
	 * @param key name of the config variable we are looking for
	 * @return returns the type if any
	 */
	@SuppressWarnings("rawtypes")
	public Class getType(String key) {
		
		if(hasValue(String.class, key)) {
			return String.class;
		} else if(hasValue(Integer.class, key)) {
			return int.class;
		} else if(hasValue(Float.class, key)) {
			return float.class;
		} else {
			return null;
		}
	}
}
